You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
6.9 KiB
229 lines
6.9 KiB
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
|
const ownerRecord = {
|
|
id: 310001,
|
|
publicSlug: "consistency-user",
|
|
nickname: "Consistency",
|
|
avatar: null,
|
|
avatarVisibility: "public",
|
|
bioVisibility: "public",
|
|
bioMarkdown: "",
|
|
socialLinksJson: "[]",
|
|
status: "active",
|
|
};
|
|
|
|
const selectMock = mock(() => ({
|
|
from: () => ({
|
|
where: () => ({
|
|
limit: async () => [ownerRecord],
|
|
}),
|
|
}),
|
|
}));
|
|
|
|
const parseSocialLinksJson = mock(() => []);
|
|
const normalizePublicListPage = mock((pageRaw: unknown) => {
|
|
const n = Number(pageRaw);
|
|
return Number.isFinite(n) && n >= 1 ? Math.trunc(n) : 1;
|
|
});
|
|
|
|
function publicTotal<T extends { visibility: string }>(rows: T[]) {
|
|
return rows.filter((row) => row.visibility === "public").length;
|
|
}
|
|
|
|
const previewPosts = [
|
|
{ slug: "preview-post-a", visibility: "public" },
|
|
{ slug: "preview-post-b", visibility: "public" },
|
|
{ slug: "preview-post-hidden", visibility: "private" },
|
|
];
|
|
const previewTimeline = [
|
|
{ id: 301, visibility: "public" },
|
|
{ id: 302, visibility: "public" },
|
|
{ id: 303, visibility: "private" },
|
|
];
|
|
const previewReading = [
|
|
{ id: 401, visibility: "public" },
|
|
{ id: 402, visibility: "public" },
|
|
{ id: 403, visibility: "unlisted" },
|
|
];
|
|
|
|
const pagePosts = [
|
|
{ slug: "page-post-1", visibility: "public" },
|
|
{ slug: "page-post-2", visibility: "private" },
|
|
{ slug: "page-post-3", visibility: "public" },
|
|
];
|
|
const pageTimeline = [
|
|
{ id: 501, visibility: "private" },
|
|
{ id: 502, visibility: "public" },
|
|
{ id: 503, visibility: "public" },
|
|
];
|
|
const pageReading = [
|
|
{ id: 601, visibility: "public" },
|
|
{ id: 602, visibility: "unlisted" },
|
|
{ id: 603, visibility: "public" },
|
|
];
|
|
|
|
const getPublicPostsPreviewBySlug = mock(async () => ({
|
|
items: previewPosts.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(previewPosts),
|
|
}));
|
|
const getPublicTimelinePreviewBySlug = mock(async () => ({
|
|
items: previewTimeline.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(previewTimeline),
|
|
}));
|
|
const getPublicRssPreviewBySlug = mock(async () => ({
|
|
items: previewReading.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(previewReading),
|
|
}));
|
|
|
|
const getPublicPostsPageBySlug = mock(async () => ({
|
|
items: pagePosts.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(pagePosts),
|
|
page: 1,
|
|
pageSize: 10,
|
|
}));
|
|
|
|
const getPublicTimelinePageBySlug = mock(async () => ({
|
|
items: pageTimeline.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(pageTimeline),
|
|
page: 1,
|
|
pageSize: 10,
|
|
}));
|
|
|
|
const getPublicRssPageBySlug = mock(async () => ({
|
|
items: pageReading.filter((row) => row.visibility === "public"),
|
|
total: publicTotal(pageReading),
|
|
page: 1,
|
|
pageSize: 10,
|
|
}));
|
|
|
|
mock.module("drizzle-pkg/lib/db", () => ({
|
|
dbGlobal: {
|
|
select: selectMock,
|
|
},
|
|
}));
|
|
|
|
mock.module("drizzle-pkg/lib/schema/auth", () => ({
|
|
users: {
|
|
id: "id",
|
|
publicSlug: "publicSlug",
|
|
status: "status",
|
|
},
|
|
}));
|
|
|
|
mock.module("drizzle-orm", () => ({
|
|
and: (...conditions: unknown[]) => conditions,
|
|
eq: (a: unknown, b: unknown) => [a, b],
|
|
}));
|
|
|
|
mock.module("#server/service/profile", () => ({
|
|
parseSocialLinksJson,
|
|
}));
|
|
|
|
let realGetPublicHubBySlug: ((...args: unknown[]) => unknown) | undefined;
|
|
|
|
mock.module("#server/service/public-hub", () => ({
|
|
getPublicHubBySlug: (...args: unknown[]) => {
|
|
if (!realGetPublicHubBySlug) {
|
|
throw new Error("realGetPublicHubBySlug not initialized");
|
|
}
|
|
return realGetPublicHubBySlug(...args);
|
|
},
|
|
}));
|
|
|
|
mock.module("#server/service/posts", () => ({
|
|
getPublicPostsPreviewBySlug,
|
|
getPublicPostsPageBySlug,
|
|
}));
|
|
|
|
mock.module("#server/service/timeline", () => ({
|
|
getPublicTimelinePreviewBySlug,
|
|
getPublicTimelinePageBySlug,
|
|
}));
|
|
|
|
mock.module("#server/service/rss", () => ({
|
|
getPublicRssPreviewBySlug,
|
|
getPublicRssPageBySlug,
|
|
}));
|
|
|
|
({ getPublicHubBySlug: realGetPublicHubBySlug } = await import("../../../../service/public-hub"));
|
|
|
|
mock.module("#server/utils/public-pagination", () => ({
|
|
normalizePublicListPage,
|
|
}));
|
|
|
|
mock.module("h3", () => ({
|
|
defineEventHandler: (handler: unknown) => handler,
|
|
createError: (input: { statusCode: number; statusMessage: string }) => {
|
|
const error = new Error(input.statusMessage) as Error & {
|
|
statusCode: number;
|
|
statusMessage: string;
|
|
};
|
|
error.statusCode = input.statusCode;
|
|
error.statusMessage = input.statusMessage;
|
|
return error;
|
|
},
|
|
getQuery: (event: { query?: Record<string, unknown> }) => event.query ?? {},
|
|
}));
|
|
|
|
globalThis.defineEventHandler ??= ((handler: unknown) => handler) as never;
|
|
globalThis.createError ??= ((input: { statusCode: number; statusMessage: string }) => {
|
|
const error = new Error(input.statusMessage) as Error & {
|
|
statusCode: number;
|
|
statusMessage: string;
|
|
};
|
|
error.statusCode = input.statusCode;
|
|
error.statusMessage = input.statusMessage;
|
|
return error;
|
|
}) as never;
|
|
globalThis.getQuery ??= ((event: { query?: Record<string, unknown> }) => event.query ?? {}) as never;
|
|
globalThis.R ??= {
|
|
success: (data: unknown) => data,
|
|
};
|
|
|
|
const profileHandler = (await import("../[publicSlug].get")).default;
|
|
const postsHandler = (await import("./posts/index.get")).default;
|
|
const timelineHandler = (await import("./timeline/index.get")).default;
|
|
const readingHandler = (await import("./reading/index.get")).default;
|
|
|
|
describe("public profile totals consistency", () => {
|
|
beforeEach(() => {
|
|
selectMock.mockClear();
|
|
parseSocialLinksJson.mockClear();
|
|
getPublicPostsPreviewBySlug.mockClear();
|
|
getPublicTimelinePreviewBySlug.mockClear();
|
|
getPublicRssPreviewBySlug.mockClear();
|
|
getPublicPostsPageBySlug.mockClear();
|
|
getPublicTimelinePageBySlug.mockClear();
|
|
getPublicRssPageBySlug.mockClear();
|
|
normalizePublicListPage.mockClear();
|
|
});
|
|
|
|
test("同一 slug 下 hub.modules.<x>.total 与对应子页 total 一致(public-only)", async () => {
|
|
const [profileRes, postsRes, timelineRes, readingRes] = await Promise.all([
|
|
profileHandler({ context: { params: { publicSlug: ownerRecord.publicSlug } } } as never),
|
|
postsHandler({
|
|
context: { params: { publicSlug: ownerRecord.publicSlug } },
|
|
query: { page: "1" },
|
|
} as never),
|
|
timelineHandler({
|
|
context: { params: { publicSlug: ownerRecord.publicSlug } },
|
|
query: { page: "1" },
|
|
} as never),
|
|
readingHandler({
|
|
context: { params: { publicSlug: ownerRecord.publicSlug } },
|
|
query: { page: "1" },
|
|
} as never),
|
|
]);
|
|
|
|
expect(profileRes.modules.posts.total).toBe(postsRes.total);
|
|
expect(profileRes.modules.timeline.total).toBe(timelineRes.total);
|
|
expect(profileRes.modules.reading.total).toBe(readingRes.total);
|
|
expect(profileRes.modules.posts.total).toBe(profileRes.posts.total);
|
|
expect(profileRes.modules.timeline.total).toBe(profileRes.timeline.total);
|
|
expect(profileRes.modules.reading.total).toBe(profileRes.rssItems.total);
|
|
|
|
expect(postsRes.total).toBe(2);
|
|
expect(timelineRes.total).toBe(2);
|
|
expect(readingRes.total).toBe(2);
|
|
});
|
|
});
|
|
|